using System.Collections;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using KeraLua;


namespace Ex
{
    public class ExCsRawData : IExCsElement, IDisposable
    {
        protected readonly EnvLua env;

        private volatile bool disposed = false;

        protected string data_class_guid;

        protected readonly Hashtable ref_list = new();

        protected readonly static ConcurrentDictionary<string, object> _class_table = new();

        public EnvLua CurrentLua
        {
            get { return env; }
        }

        public readonly object Data;

        protected ExCsRawData(EnvLua lua, object data, string data_class_guid)
        {
            this.env = lua;
            this.Data = data;
            this.data_class_guid = data_class_guid;
        }

        protected static ExCsRawData? CheckValid(nint ptr, out GCHandle? gc_ptr)
        {
            if (ptr == IntPtr.Zero)
            {
                gc_ptr = null;
                return null;
            }
            if (Marshal.PtrToStructure(ptr, typeof(GCHandle)) is GCHandle _gc_ptr && _gc_ptr.Target is ExCsRawData data)
            {
                gc_ptr = _gc_ptr;
                if (data.disposed)
                {
                    throw new Exception("object already disposed");
                }
                if (!data.ref_list.ContainsKey(ptr))
                {
                    throw new Exception("reference not found or already disposed");
                }
                return data;
            }
            gc_ptr = null;
            return null;
        }

        protected static ExCsRawData? CheckValid(nint ptr)
        {
            return CheckValid(ptr, out GCHandle? gc_ptr);
        }

        public bool IsType(ExCsRawData data)
        {
            return this.data_class_guid == data.data_class_guid;
        }

        public bool IsType(Type type)
        {
            return this.data_class_guid == type.GUID.ToString();
        }

        protected static int LuaUnregister(Lua lua, nint status)
        {
            var ptr = lua.ToUserData(-1);
            lua.Pop(1);
            if (CheckValid(ptr, out GCHandle? gc_ptr) is ExCsRawData data && gc_ptr != null)
            {
                if (data.ref_list.ContainsKey(ptr) && gc_ptr.Value.IsAllocated)
                {
                    gc_ptr.Value.Free();
                    data.ref_list.Remove(ptr);
                }
            }
            return 0;
        }

        //thread unsafe
        protected int GetMetatable()
        {
            return GetMetatable(env, data_class_guid);
        }

        //thread unsafe
        protected static int GetMetatable(EnvLua env, string data_class_guid)
        {

            lock (env)
            {
                // registry
                env.Current.PushCopy((int)LuaRegistry.Index);

                // registry | key
                env.Current.PushString(data_class_guid);

                // registry | (metatable or nil)
                env.Current.GetTable(-2);

                if (!env.Current.IsTable(-1))
                {
                    return 0;
                }

                // registry | metatable
                env.Current.Remove(-2);
                // metatable
                return 1;
            }
        }


        public void PushToLuaStack()
        {
            lock (env)
            {
                if (disposed)
                {
                    throw new Exception("unexpected error: data is disposed");
                }
                var ptr = env.Current.NewUserData(Unsafe.SizeOf<GCHandle>());
                if (GetMetatable() == 0)
                {
                    throw new Exception("unexpected error: metatable not found");
                }
                env.Current.SetMetaTable(-2);

                var gc_ptr = GCHandle.Alloc(this);
                Marshal.StructureToPtr(gc_ptr, ptr, false);
                ref_list.Add(ptr, gc_ptr);
            }
        }

        public static ExCsRawData WatchFromLuaStack(EnvLua env, int index)
        {
            lock (env)
            {
                if (env.Current.GetMetaField(index, "__name") is LuaType.Nil)
                {
                    throw new InvalidDataException("Wrong type to fetch");
                }
                var guid = env.Current.ToString(-1);
                if (!_class_table.ContainsKey(guid))
                {
                    throw new Exception("unexpected error: class_table not contains key");
                }
                env.Current.Pop(1);
                var ptr = env.Current.ToUserData(index);
                if (ptr == IntPtr.Zero)
                {
                    throw new Exception("unexpected error: invalid ptr");
                }
                GCHandle data_gc_ptr = (GCHandle)Marshal.PtrToStructure(ptr, typeof(GCHandle))!;
                return (ExCsRawData)data_gc_ptr.Target!;
            }
        }

        public static ExCsRawData FetchFromLuaStack(EnvLua env)
        {
            lock (env)
            {
                var result = WatchFromLuaStack(env, -1);
                env.Current.Pop(1);
                return result;
            }
        }
        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    foreach (var obj in ref_list.Values)
                    {
                        if (obj is GCHandle gc_ptr && gc_ptr.IsAllocated)
                        {
                            gc_ptr.Free();
                        }
                    }
                    ref_list.Clear();
                }
                disposed = true;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~ExCsRawData() => Dispose(false);
    }

}